<script lang="ts" setup>
import type { DialogBeforeCloseFn } from 'element-plus';
import { isPromise } from 'element-plus/es/utils/types.mjs';
defineOptions({ name: 'PromiseDialog' });

const props = defineProps<{
  /**
   * 弹窗关闭前回调，返回false或Promise抛出错误则阻止关闭
   */
  beforeClose?: () => boolean | void | undefined | Promise<boolean | void | undefined>;
}>();

const dialogRef = shallowRef<InstanceType<GlobalComponents['ElDialog']>>();

type VoidFn<T extends any[]> = (...args: T) => void;

let resolve: VoidFn<[]> | undefined;
let reject: VoidFn<[]> | undefined;

const visible = ref(false);

const open = async () => {
  reject?.();
  await nextTick();
  visible.value = true;
  await new Promise<void>((_resolve, _reject) => {
    resolve = () => {
      resolve = undefined;
      reject = undefined;
      visible.value = false;
      _resolve();
    };
    reject = () => {
      resolve = undefined;
      reject = undefined;
      visible.value = false;
      _reject(new Error('主动关闭弹窗reject,并非业务错误'));
    };
  });
};

const complete = () => resolve?.();

/**
 * 尝试关闭弹窗，执行beforeClose判断是否关闭，若关闭成功则`open`函数的promise会`reject`
 * @param force 跳过beforeClose回调，强制关闭
 */
const close = async (force: boolean = false) => {
  if (force) {
    reject?.();
  } else {
    let bool: any;
    if (props.beforeClose) {
      const res = props.beforeClose();
      if (isPromise(res)) {
        bool = await res;
      } else {
        bool = res;
      }
    }
    if (bool !== false) {
      reject?.();
    }
  }
};

defineExpose({
  open,
  complete,
  close,
});

const _beforeClose: DialogBeforeCloseFn = async (done: any) => {
  try {
    await close();
    done(false);
  } catch (error) {
    done(true);
    console.error(error);
  }
};
</script>

<template>
  <el-dialog
    ref="dialogRef"
    v-model="visible"
    class="promise-dialog"
    :close-on-click-modal="false"
    append-to="body"
    destroy-on-close
    align-center
    v-bind="$.vnode.props"
    :before-close="_beforeClose"
  >
    <template v-for="(_value, key) in $slots" #[key]>
      <slot :name="key" />
    </template>
  </el-dialog>
</template>
